from .doubly_linked_base import _DoublyLinkedBase


class PositionalList(_DoublyLinkedBase):
    """An sequential container of elements allowing positional access"""

    class Position:
        """An abstraction representing the location of a single element"""
        def __init__(self, container, node):
            self._container = container
            self._node = node

        def element(self):
            return self._node._element

        def __eq__(self, other):
            """Return true if other is a Position representing the same location"""
            return type(other) is type(self) and other._node is self._node

        def __ne__(self, other):
            return not (self == other)

    def _validate(self, p):
        """
        Return postion's node
        """
        if not isinstance(p, self.Position):
            raise TypeError("p is not proper Position type")
        if p._container is not self:
            raise ValueError("p dose not belong to this container")
        if p._node._next is None:
            raise ValueError("p is no longer valid")
        return p._node

    def _make_position(self, node):
        """Return Position instance"""
        if node is self._header or node is self._tailer:
            return None
        else:
            return self.Position(self, node)

    def first(self):
        """Return the first Position in the list"""
        return self._make_position(self._header._next)

    def last(self):
        """Return the last Position in the list"""
        return self._make_position(self._tailer._prev)

    def before(self, p):
        """Return the Position just before Postion p"""
        node = self._validate(p)
        return self._make_position(node._prev)

    def after(self, p):
        """Return the Position just after Position p"""
        node = self._validate(p)
        return self._make_position(node._next)

    def __iter__(self):
        """Generate forward iter"""
        walk = self.first()
        while walk is not None:
            yield walk.element()
            walk = self.after(walk)

    def __reversed__(self):
        walk = self.last()
        while walk is not None:
            yield walk.element()
            walk = self.before(walk)

    def _insert_between(self, element, predecessor, successor):
        """Add element between two existed nodes"""
        node = super()._insert_between(element, predecessor, successor)
        return self._make_position(node)

    def add_first(self, element):
        """Add element at the front of the list"""
        return self._insert_between(element, self._header, self._header._next)

    def add_last(self, element):
        """Add element at the tail of the list"""
        return self._insert_between(element, self._tailer._prev, self._tailer)

    def add_before(self, p, element):
        """Insert element into list before Position p"""
        valid_p = self._validate(p)
        return self._insert_between(element, valid_p._prev, valid_p)

    def add_after(self, p, element):
        """Insert element into list after Position p"""
        valid_p = self._validate(p)
        return self._insert_between(element, valid_p, valid_p._next)

    def delete(self, p):
        """Remove and return the element at the Position p"""
        valid_p = self._validate(p)
        return self._delete_node(valid_p)

    def replace(self, p, element):
        """Replace the elemnt at Posistion p with e"""
        valid_p = self._validate(p)
        old_value = valid_p._element
        valid_p._element = element
        return old_value

    def find(self, element):
        """Find first position of the element in the list"""
        walk = self._header._next
        while walk is not self._tailer and walk._element != element:
            walk = walk._next
        if walk is self._tailer:
            return None
        else:
            return self._make_position(walk)


